package domain;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author alex.alvarez.chinchilla
 */

public class Model implements Serializable{
    //Attributes
    protected Trie<Pair<Trie<ModelRule>, Integer>> _rulesTrie;
    protected boolean _generatingModel = false;
    protected List<String> _listTextAlpha = null;
    protected Integer _numOfRules;
    protected Integer _MAXN;

    //Associations
    protected TextAlpha _textAlpha= null;


    protected Model(TextAlpha tA) {
        _textAlpha=tA;
    }

    private int getN() {
        return _MAXN;
    }

    protected Integer getNumOfRules(){
        return _numOfRules;
    }

    /**
    *   Consulta la provabilitat de que, volent escriure alpha s'hagi escrit beta.
    *   Retorna 1.0 (Provabilitat màxima) si alpha==beta i 0.0 si no existeix.
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és la paraula correcta
    * @param beta és la paraula incorrecta
    * @return un Float amb la provabilitat de que, volent escriure alpha s'hagi escrit beta.
    * @throws -
    */
    protected Float getProbability(String alpha, String beta) {
        Float probability = (float) 0.0;
        Integer probA, probB;
        Pair<Trie<ModelRule>, Integer> betaTrieCount = _rulesTrie.get(alpha);
        if (alpha.equals(beta)) {
            probability = (float) 1.0;
        } else if (betaTrieCount != null) {
            ModelRule betaRule = betaTrieCount.first.get(beta);
            if (betaRule != null) {
                probA = betaTrieCount.second;
                if (probA > 0) {
                    probB = betaRule.getProbability();
                    probability = (float) probB / probA;
                }
            }
        }
        return probability;
    }

    /**
    *   Calcula el número d'ocurrencies d'un substring a un string.
    *
    * @author alex.alvarez.chinchilla
    * @param string és l'string
    * @param sstring és el substring
    * @return un Integer amb el número d'ocurrencies d'un substring a un string.
    * @throws -
    */
    private Integer numOfOcurrences(String string, String sstring) {
        int n = 0;
        boolean finish = false;
        int position = 0;
        while (position < string.length() && !finish) {
            position = string.indexOf(sstring, position);
            if (position != -1) {
                ++n;
                ++position;
            } else {
                finish = true;
            }

        }
        return n;
    }

    /**
    *   Calcula el número d'ocurrencies d'alpha en un textAlpha.
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és un string
    * @return un Integer amb el número d'ocurrencies d'alpha en un textAlpha.
    * @throws -
    */
    private Integer getAlphaProbability(String alpha) {
        if (!_generatingModel) {
            _listTextAlpha = _textAlpha.getWordsOfTextAlpha();
        }
        int nA = 0;
        for (String s : _listTextAlpha) {
            nA += numOfOcurrences(s, alpha);
        }
        if (nA == 0) {
            nA = 1; //evita división entre 0
        }
        return nA;

    }

    private char getSymbol() {
        return Constants.getSYMBOL();
    }

    /**
    *   Retorna el mínim entre 3 nombres
    *
    * @author ruben.bordallo
    * @param a es un número
    * @param b es un número
    * @param c es un número
    * @return el nombre més petit entre a,b i c
    * @throws -
    */
    private static int minimum(int a, int b, int c) {
	if (a <= b && a <= c) {
	    return a;
	}
	if (b <= a && b <= c) {
	    return b;
	}
	return c;
    }


    /**
    *   Genera el Model a partir d'una llista de parells de training utilitzan
    *   el _textAlpha com a font de probabilitat d'alpha
    *
    * @author alex.alvarez.chinchilla
    * @param trainingPairsList es la llista de parells de training
    */
    protected void generateModel(ArrayList<Pair<String, String>> trainingPairsList) {
        _generatingModel = true;
        _rulesTrie = new Trie<Pair<Trie<ModelRule>, Integer>>();
        _MAXN=Constants.getMAXN();
        _numOfRules=0;
        _listTextAlpha = _textAlpha.getWordsOfTextAlpha();
        for (Pair<String, String> pair : trainingPairsList) {
            Pair<String, String> l = getLinealisation(pair.second, pair.first);
            substrings(l.first, l.second);

        }
        _listTextAlpha = null;
        _generatingModel = false;
    }

    /**
    *   Genera la linealització més òptima entre dues paraules, és a dir,
    *   el mínim nombre d'insercions, eliminacions o transposicions entre
    *   lletres de la paraula per transformar-la en l'altra
    *
    * @author ruben.bordallo
    * @param st1 és la paraula base
    * @param st2 és la paraula que es vol obtenir
    * @return un Pair amb les dues paraules amb el caràcter C com a indicador
    *           d'una inserció
    * @throws -
    */
    private Pair<String,String> getLinealisation(String st1, String st2) {
	char C = getSymbol();
	boolean swapped = false;

        if (st1.length() > st2.length()) {//ponemos la mas larga en el st2
            String temp = st1;
            st1 = st2;
            st2 = temp;
            swapped = true;
        }

        char[] str1 = st1.toCharArray();   //aplicamos levenshtein
        char[] str2 = st2.toCharArray();

        int[][] distance = new int[str1.length + 1][str2.length + 1];

	for (int i = 0; i <= str1.length; i++) {
	    distance[i][0] = i;
	}
	for (int j = 0; j <= str2.length; j++) {
	    distance[0][j] = j;
	}
	for (int i = 1; i <= str1.length; i++) {
	    for (int j = 1; j <= str2.length; j++) {
		distance[i][j] = minimum(distance[i - 1][j] + 1,
			distance[i][j - 1] + 1,
			distance[i - 1][j - 1] + ((str1[i - 1] == str2[j - 1]) ? 0 : 1));
	    }
	}
        String p1, p2;
        p1 = "";  //strings para las alineaciones
        p2 = "";

        int x = str1.length; //indices
        int y = str2.length;

        st1 = C + st1;  //añadimos a los originales un caracter para cuadrarlos con la matriz
        st2 = C + st2;

        while (x > 0 || y > 0) {
            //direccion = 1 si es diagonal
            //direccion = 0 si es horizontal
            //direccion = 2 si es vertical

            //probamos a comenzar por la diagonal
            int x_aux = x - 1;
            int y_aux = y - 1;
            int direccion = 1;

            //si nos salimos de la array probamos con la vertical
            if (y - 1 < 0) {
                x_aux = x - 1;
                y_aux = y;
                direccion = 2;

            }//si nos salimos por arriba probamos la horizontal
            else if (x - 1 < 0) {
                x_aux = x;
                y_aux = y - 1;
                direccion = 0;
            }

            //buscamos la mas pequeña para continuar
            if (y > 0 && distance[x][y - 1] < distance[x_aux][y_aux]) { //horizontal
                x_aux = x;
                y_aux = y - 1;
                direccion = 0;
            } else if (x > 0 && distance[x - 1][y] < distance[x_aux][y_aux]) {//vertical
                x_aux = x - 1;
                y_aux = y;
                direccion = 2;
            }

            //montamos los strings alineados
            if (distance[x_aux][y_aux] == distance[x][y] || direccion == 1) {//diagonal
                p1 = st1.charAt(x) + p1;
                p2 = st2.charAt(y) + p2;
            } else if (direccion == 0) { //horizontal
                p1 = C + p1;
                p2 = st2.charAt(y) + p2;
            } else { //vertical
                p2 = C + p2;
                p1 = st1.charAt(x) + p1;
            }

            //nos movemos a la casilla mas optima
            x = x_aux;
            y = y_aux;
        }

	if (swapped) {
        Pair<String,String> p = new Pair <String,String> (p2, p1);
        return p;
	} else {
	    Pair<String,String> p = new Pair <String,String> (p1, p2);
        return p;
	}
    }

    /**
    *   Calcula totes les normes donades unes alpha i beta linealitzades.
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és un string
    * @param beta és un string
    */
    private void substrings(String alpha, String beta) {
        int n=getN();
        for (int i = n; i > 0; i--) {
            substrings(alpha.toCharArray(), beta.toCharArray(), i, 0);
        }
    }

    /**
    *   Calcula totes les normes de tamany n donades unes alpha i beta
    *   linealitzades a partir d'una posició begin
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és una cadena de caràcters
    * @param beta és una cadena de caràcters
    * @param n és la N amb la que es volen les normes
    * @param begin es un enter que marca on començar a normalitzar
    */
    private void substrings(char[] alpha, char[] beta, int n, int begin) {
        if (begin + n - 1 < alpha.length) {
            int i, end;
            char c = getSymbol();
            int m = 0; // es el contador de caracteres de alfa que llevamos. "Subparte de n"
            i = begin;
            end = begin;
            if (n > 1) {
                end = begin + 1;
            }
            int next = begin + 1;
            boolean finish = false;
            boolean nomoresubstrings = false;//evita la repeticion del lanzamiento de substrings
            //cuando se suceden muchos asteriscos
            boolean nextsubstrings = true; /*hay un caso en el que no nos interesa
            lanzar los siguientes ya que se lanzaria dos veces*/
            boolean correctrule = false; //solo es norma cuando m=n
            while ((m <= n) && !finish && i < alpha.length) {
                //diferenciamos dos casos m==n y m < n
                if (m == n) {
                    correctrule = true;
                    if (alpha[i] == c) {
                        if (i < alpha.length) {
                            end = i;
                        }
                        int aas = 0; //numero de supresiones/inserciones de alfa
                        int bas = 0; //numero de supresiones/inserciones de beta
                        int na = 0; // complementario a na
                        boolean valid = false;
                        for (int j = begin; j <= end && !valid; j++) {
                            if (alpha[j] != c) {
                                na++;
                            }

                            if (alpha[j] != beta[j]) {
                                valid = true;
                                if (alpha[j] == c) {
                                    ++aas;
                                }
                                if (beta[j] == c) {
                                    ++bas;
                                }
                            }
                        }
                        if (aas == (end - begin + 1) || bas == (end - begin + 1) || na != n) {
                            //evitar segmentation fault
                            valid = false;
                        }
                        if (end <= alpha.length - 2) {
                            //para evitar que repita en el caso nom***r nombpar
                            if (alpha[end + 1] != c) {
                                valid = false;
                            }
                        }
                        if (end == alpha.length - 1) {
                            //para evitar que repita en el caso que acabe con *
                            valid = false;
                        }
                        if (valid) {
                            saveRule(alpha, beta, begin, end);
                        }
                        i++; //puede provocar segmentationfault pero está controlado
                    } else {
                        i--;// en el caso de que m=n y encontramos una letra el substring acaba en la anterior
                        finish = true;
                    }
                } else {
                    if (alpha[i] == c) {
                        i++;
                        if (i == alpha.length) {
                            i--;
                            finish = true;
                        }
                        if (m == 0 && !nomoresubstrings) {
                            substrings(alpha, beta, n, i);
                            nextsubstrings = false; //evita repeticiones de normas
                            nomoresubstrings = true;
                        }
                    } else {
                        m++;
                        i++;
                        if (m == 1) {
                            next = i;
                        }

                        if (i == alpha.length) {
                            i--;
                            finish = true;
                        }
                    }
                }
            }

            if (i < alpha.length) {
                end = i;
            }
            int aas = 0; //numero de supresiones/inserciones de alfa
            int bas = 0; //numero de supresiones/inserciones de beta
            int na = 0;

            boolean valida = false;
            for (int j = begin; j <= end; j++) {
                if (alpha[j] != c) {
                    na++;
                }

                if (alpha[j] != beta[j]) {
                    valida = true;
                    if (alpha[j] == c) {
                        ++aas;
                    }
                    if (beta[j] == c) {
                        ++bas;
                    }
                }
            }
            if (aas == (end - begin + 1) || bas == (end - begin + 1) || na != n) {
                valida = false;
            }
            if (valida) {
                saveRule(alpha, beta, begin, end);
            }

            if (next < alpha.length && nextsubstrings && correctrule) {
                int numast = 0;
                for (int k = next; k < alpha.length; k++) {
                    if (alpha[k] == c) {
                        ++numast;
                    }
                }
                //si todo lo que quedan son asteriscos no llamamos sobre ese substring
                if (numast < alpha.length - next) {
                    substrings(alpha, beta, n, next);
                }
            }
        }
    }

    /**
    *   Guarda al model la norma entre les posicions begin i end tot i eliminant
    *   el caràcter especial.
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és una cadena de caràcters
    * @param beta és una cadena de caràcters
    * @param begin és un enter que marca on comença la norma
    * @param begin és un enter que marca on acaba la norma
    */
    private void saveRule(char[] alpha, char[] beta, int begin, int end) {
        char c = getSymbol();
        String strBeta = "";
        for (int j = begin; j <= end; j++) {
            if (beta[j] != c) {
                strBeta += beta[j];
            }
        }
        String strAlpha = "";
        for (int j = begin; j <= end; j++) {
            if (alpha[j] != c) {
                strAlpha += alpha[j];
            }
        }
        saveRule(strAlpha,strBeta);
    }

    /**
    *   Guarda al model la norma alpha-->beta
    *
    *
    * @author alex.alvarez.chinchilla
    * @param alpha és una cadena de caràcters
    * @param beta és una cadena de caràcters
    */
    protected void saveRule(String strAlpha, String strBeta){
        ModelRule betaRule;
        Pair<Trie<ModelRule>, Integer> alphaTrieCount = _rulesTrie.get(strAlpha);
        Trie<ModelRule> alphaTrie;
        if (alphaTrieCount == null) {
            alphaTrie = new Trie<ModelRule>();
            betaRule = new ModelRule(strAlpha, strBeta);
            alphaTrie.put(strBeta, betaRule);
            alphaTrieCount = new Pair<Trie<ModelRule>, Integer>();
            alphaTrieCount.first = alphaTrie;
            alphaTrieCount.second = getAlphaProbability(strAlpha);
            _rulesTrie.put(strAlpha, alphaTrieCount);
            ++_numOfRules;
        } else {
            alphaTrie = alphaTrieCount.first;
            betaRule = alphaTrie.get(strBeta);
            if (betaRule == null) {
                betaRule = new ModelRule(strAlpha, strBeta);
                ++_numOfRules;
            } else {
                betaRule.incrProbability();
            }
            alphaTrie.put(strBeta, betaRule);
            alphaTrieCount.first = alphaTrie;
            alphaTrieCount.second++;
            _rulesTrie.put(strAlpha, alphaTrieCount);

        }
       }

    protected ArrayList<Pair<String,String>> printRules() {
        ArrayList<Pair<String,String>> rulesList= new ArrayList<Pair<String,String>>();
        for (Pair<Trie<ModelRule>, Integer> beta : _rulesTrie.getAll()) {
            for (ModelRule rule : beta.first.getAll()) {
                rulesList.add(new Pair<String,String>(rule.getAlpha(),rule.getBeta()));
            }
        }
        return rulesList;
    }
}
